﻿using System;
using Adaptive.Agrona;
using Adaptive.Agrona.Concurrent;
using Unity.Type.BinaryType;
using Xunit;

namespace Unity.Type.Tests.BinaryType
{
    public class StatusTest
    {
        private const int TestBufferCapacity = 1024;
        private const int TestChangerCsiId = 123456;
        private const int TestPublishedStreamIds = 10;
        private const int TestPublishedOfflineIds = 20;
        private const int TestDroppedIds = 1;
        private const long TestTimeDrift = 321;
        private const string TestFingerprint = "Test Fingerprint";
        private const string TestIdent = "Test Ident";
        private const string TestVersion = "Test Version";
        
        private readonly byte[] _testFingerprintByteArray = 
            System.Text.Encoding.ASCII.GetBytes(TestFingerprint);
        
        private readonly byte[] _testIdentByteArray = 
            System.Text.Encoding.ASCII.GetBytes(TestIdent);
        
        private readonly byte[] _testVersionByteArray = 
            System.Text.Encoding.ASCII.GetBytes(TestVersion);
        
        private const GlobalStatus TestGlobalStatus = GlobalStatus.ForcedOffline;
        private const int TestAllGenerated = 30;
        private const string ExpectedEmptyString = "";
        private const string ExpectedRequiredString = "required";


        [Fact]
        public void EncoderTest()
        {
            Status testStatus = new Status();
            
            IMutableDirectBuffer testBuffer = new UnsafeBuffer(new byte[TestBufferCapacity]);
            int testOffset = 0;
            MessageHeader testMessageHeader = new MessageHeader();
            testStatus.WrapForEncodeAndApplyHeader(testBuffer, testOffset, testMessageHeader);
            
            Assert.Equal(MessageHeader.Size, testStatus.Offset);
            Assert.Equal(Status.BlockLength + MessageHeader.Size, testStatus.Limit);
            Assert.Equal(Status.BlockLength, testStatus.Size);

            testStatus.Limit = TestBufferCapacity;
            Assert.Equal(TestBufferCapacity, testStatus.Limit);
            
            Assert.NotEmpty(testStatus.ToString());
        }

        private static string ReadStringFromNullTerminatedSpan(ReadOnlySpan<byte> value)
        {
            int length = 0;
            foreach (var symbol in value)
            {
                if (symbol != 0)
                {
                    length++;
                }
            }

            byte[] buffer = new byte[length];
            if (length > 0)
            {
                for (int i = 0; i < length; i++)
                {
                    buffer[i] = value[i];
                }
                
            }

            return System.Text.Encoding.ASCII.GetString(buffer);
        }
        
        [Fact]
        public void DecoderTest()
        {
            Status testStatus = new Status();

            IMutableDirectBuffer testBuffer = new UnsafeBuffer(new byte[TestBufferCapacity]);
            int testOffset = 0;
            testStatus.WrapForDecode(testBuffer, testOffset, Status.BlockLength, Status.SchemaVersion);
            
            Assert.Equal(testOffset, testStatus.Offset);

            Assert.Equal(Status.BlockLength, testStatus.Limit);
            Assert.Equal(Status.BlockLength, testStatus.Size);
            
            testStatus.Limit = TestBufferCapacity;
            Assert.Equal(TestBufferCapacity, testStatus.Limit);

            testStatus.ChangerCsiId = TestChangerCsiId;
            Assert.Equal(TestChangerCsiId, testStatus.ChangerCsiId);
            
            testStatus.PublishedStreamIDs = TestPublishedStreamIds;
            Assert.Equal(TestPublishedStreamIds, testStatus.PublishedStreamIDs);
            
            testStatus.PublishedOfflineIds = TestPublishedOfflineIds;
            Assert.Equal(TestPublishedOfflineIds, testStatus.PublishedOfflineIds);
            
            testStatus.DroppedIDs = TestDroppedIds;
            Assert.Equal(TestDroppedIds, testStatus.DroppedIDs);
            
            testStatus.TimeDrift = TestTimeDrift;
            Assert.Equal(TestTimeDrift, testStatus.TimeDrift);
            
            testStatus.Fingerprint = _testFingerprintByteArray;
            Assert.Equal(TestFingerprint, ReadStringFromNullTerminatedSpan(testStatus.Fingerprint));
            
            testStatus.Ident = _testIdentByteArray;
            //Assert.Equal(TestIdent, ReadStringFromNullTerminatedSpan(testStatus.Ident));
            
            testStatus.Version = _testVersionByteArray;
            //Assert.Equal(TestVersion, ReadStringFromNullTerminatedSpan(testStatus.Version));
            
            testStatus.GlobalStatus = TestGlobalStatus;
            Assert.Equal(TestGlobalStatus, testStatus.GlobalStatus);
            
            testStatus.AllGenerated = TestAllGenerated;
            Assert.Equal(TestAllGenerated, testStatus.AllGenerated);
            
            Assert.NotEmpty(testStatus.ToString());
        }

        [Fact]
        public void StaticMethodsTest()
        {
            Assert.Equal(ExpectedEmptyString, Status.ChangerCsiIdMetaAttribute(MetaAttribute.Epoch));
            Assert.Equal(ExpectedEmptyString, Status.ChangerCsiIdMetaAttribute(MetaAttribute.TimeUnit));
            Assert.Equal(ExpectedEmptyString, Status.ChangerCsiIdMetaAttribute(MetaAttribute.SemanticType));
            Assert.Equal(ExpectedRequiredString, Status.ChangerCsiIdMetaAttribute(MetaAttribute.Presence));
            
            Assert.Equal(ExpectedEmptyString, Status.PublishedStreamIDsMetaAttribute(MetaAttribute.Epoch));
            Assert.Equal(ExpectedEmptyString, Status.PublishedStreamIDsMetaAttribute(MetaAttribute.TimeUnit));
            Assert.Equal(ExpectedEmptyString, Status.PublishedStreamIDsMetaAttribute(MetaAttribute.SemanticType));
            Assert.Equal(ExpectedRequiredString, Status.PublishedStreamIDsMetaAttribute(MetaAttribute.Presence));
            
            Assert.Equal(ExpectedEmptyString, Status.PublishedOfflineIdsMetaAttribute(MetaAttribute.Epoch));
            Assert.Equal(ExpectedEmptyString, Status.PublishedOfflineIdsMetaAttribute(MetaAttribute.TimeUnit));
            Assert.Equal(ExpectedEmptyString, Status.PublishedOfflineIdsMetaAttribute(MetaAttribute.SemanticType));
            Assert.Equal(ExpectedRequiredString, Status.PublishedOfflineIdsMetaAttribute(MetaAttribute.Presence));
            
            Assert.Equal(ExpectedEmptyString, Status.DroppedIDsMetaAttribute(MetaAttribute.Epoch));
            Assert.Equal(ExpectedEmptyString, Status.DroppedIDsMetaAttribute(MetaAttribute.TimeUnit));
            Assert.Equal(ExpectedEmptyString, Status.DroppedIDsMetaAttribute(MetaAttribute.SemanticType));
            Assert.Equal(ExpectedRequiredString, Status.DroppedIDsMetaAttribute(MetaAttribute.Presence));
            
            Assert.Equal(ExpectedEmptyString, Status.TimeDriftMetaAttribute(MetaAttribute.Epoch));
            Assert.Equal(ExpectedEmptyString, Status.TimeDriftMetaAttribute(MetaAttribute.TimeUnit));
            Assert.Equal(ExpectedEmptyString, Status.TimeDriftMetaAttribute(MetaAttribute.SemanticType));
            Assert.Equal(ExpectedRequiredString, Status.TimeDriftMetaAttribute(MetaAttribute.Presence));
            
            Assert.Equal(ExpectedEmptyString, Status.FingerprintMetaAttribute(MetaAttribute.Epoch));
            Assert.Equal(ExpectedEmptyString, Status.FingerprintMetaAttribute(MetaAttribute.TimeUnit));
            Assert.Equal(ExpectedEmptyString, Status.FingerprintMetaAttribute(MetaAttribute.SemanticType));
            Assert.Equal(ExpectedRequiredString, Status.FingerprintMetaAttribute(MetaAttribute.Presence));
            
            Assert.Equal(ExpectedEmptyString, Status.GlobalStatusMetaAttribute(MetaAttribute.Epoch));
            Assert.Equal(ExpectedEmptyString, Status.GlobalStatusMetaAttribute(MetaAttribute.TimeUnit));
            Assert.Equal(ExpectedEmptyString, Status.GlobalStatusMetaAttribute(MetaAttribute.SemanticType));
            Assert.Equal(ExpectedRequiredString, Status.GlobalStatusMetaAttribute(MetaAttribute.Presence));
            
            Assert.Equal(ExpectedEmptyString, Status.IdentMetaAttribute(MetaAttribute.Epoch));
            Assert.Equal(ExpectedEmptyString, Status.IdentMetaAttribute(MetaAttribute.TimeUnit));
            Assert.Equal(ExpectedEmptyString, Status.IdentMetaAttribute(MetaAttribute.SemanticType));
            Assert.Equal(ExpectedRequiredString, Status.IdentMetaAttribute(MetaAttribute.Presence));
            
            Assert.Equal(ExpectedEmptyString, Status.VersionMetaAttribute(MetaAttribute.Epoch));
            Assert.Equal(ExpectedEmptyString, Status.VersionMetaAttribute(MetaAttribute.TimeUnit));
            Assert.Equal(ExpectedEmptyString, Status.VersionMetaAttribute(MetaAttribute.SemanticType));
            Assert.Equal(ExpectedRequiredString, Status.VersionMetaAttribute(MetaAttribute.Presence));
            
            Assert.Equal(ExpectedEmptyString, Status.AllGeneratedMetaAttribute(MetaAttribute.Epoch));
            Assert.Equal(ExpectedEmptyString, Status.AllGeneratedMetaAttribute(MetaAttribute.TimeUnit));
            Assert.Equal(ExpectedEmptyString, Status.AllGeneratedMetaAttribute(MetaAttribute.SemanticType));
            Assert.Equal(ExpectedRequiredString, Status.AllGeneratedMetaAttribute(MetaAttribute.Presence));
            
            Assert.Equal(ExpectedEmptyString, Status.OnlineBufferSizeMetaAttribute(MetaAttribute.Epoch));
            Assert.Equal(ExpectedEmptyString, Status.OnlineBufferSizeMetaAttribute(MetaAttribute.TimeUnit));
            Assert.Equal(ExpectedEmptyString, Status.OnlineBufferSizeMetaAttribute(MetaAttribute.SemanticType));
            Assert.Equal(ExpectedRequiredString, Status.OnlineBufferSizeMetaAttribute(MetaAttribute.Presence));
            
            Assert.Equal(ExpectedEmptyString, Status.OnlineBufferCapacityMetaAttribute(MetaAttribute.Epoch));
            Assert.Equal(ExpectedEmptyString, Status.OnlineBufferCapacityMetaAttribute(MetaAttribute.TimeUnit));
            Assert.Equal(ExpectedEmptyString, Status.OnlineBufferCapacityMetaAttribute(MetaAttribute.SemanticType));
            Assert.Equal(ExpectedRequiredString, Status.OnlineBufferCapacityMetaAttribute(MetaAttribute.Presence));
            
            Assert.Equal(ExpectedEmptyString, Status.OfflineBufferSizeMetaAttribute(MetaAttribute.Epoch));
            Assert.Equal(ExpectedEmptyString, Status.OfflineBufferSizeMetaAttribute(MetaAttribute.TimeUnit));
            Assert.Equal(ExpectedEmptyString, Status.OfflineBufferSizeMetaAttribute(MetaAttribute.SemanticType));
            Assert.Equal(ExpectedRequiredString, Status.OfflineBufferSizeMetaAttribute(MetaAttribute.Presence));
            
            Assert.Equal(ExpectedEmptyString, Status.OfflineBufferCapacityMetaAttribute(MetaAttribute.Epoch));
            Assert.Equal(ExpectedEmptyString, Status.OfflineBufferCapacityMetaAttribute(MetaAttribute.TimeUnit));
            Assert.Equal(ExpectedEmptyString, Status.OfflineBufferCapacityMetaAttribute(MetaAttribute.SemanticType));
            Assert.Equal(ExpectedRequiredString, Status.OfflineBufferCapacityMetaAttribute(MetaAttribute.Presence));
        }

        [Fact]
        public void ActingVersionTest()
        {
            Status testStatus = new Status();
            IMutableDirectBuffer testBuffer = new UnsafeBuffer(new byte[TestBufferCapacity]);
            testStatus.WrapForEncode(testBuffer, 0);
            Assert.True(testStatus.ChangerCsiIdInActingVersion());
            Assert.True(testStatus.PublishedStreamIDsInActingVersion());
            Assert.True(testStatus.PublishedOfflineIdsInActingVersion());
            Assert.True(testStatus.DroppedIDsInActingVersion());
            Assert.True(testStatus.TimeDriftInActingVersion());
            Assert.True(testStatus.FingerprintInActingVersion());
            Assert.True(testStatus.GlobalStatusInActingVersion());
            Assert.True(testStatus.IdentInActingVersion());
            Assert.True(testStatus.VersionInActingVersion());
            Assert.True(testStatus.AllGeneratedInActingVersion());
            Assert.True(testStatus.OnlineBufferSizeInActingVersion());
            Assert.True(testStatus.OnlineBufferCapacityInActingVersion());
            Assert.True(testStatus.OfflineBufferSizeInActingVersion());
            Assert.True(testStatus.OfflineBufferCapacityInActingVersion());
        }

        [Fact]
        public void GetAndSetMethods()
        {
            Status testStatus = new Status();
            IMutableDirectBuffer testBuffer = new UnsafeBuffer(new byte[TestBufferCapacity]);
            testStatus.WrapForEncode(testBuffer, 0);

            ReadOnlySpan<byte> testSpanValue = new ReadOnlySpan<byte>(_testFingerprintByteArray);
            testStatus.SetFingerprint(testSpanValue);
            Span<byte> testResultSpanValue = new Span<byte>(new byte[256]);
            testStatus.GetFingerprint(testResultSpanValue);
            //Assert.Equal(testSpanValue.ToArray(), testResultSpanValue.ToArray());
            
            //testStatus.SetFingerprint(_testFingerprintByteArray, 0);
            //testStatus.SetFingerprint(0, 0);

            testStatus.SetFingerprint(TestFingerprint);
            string testResultFingerprintStringValue = testStatus.GetFingerprint();
            //Assert.Equal(testResultFingerprintStringValue, TestFingerprint);

            Exception caughtException;
            caughtException = Record.Exception(() =>
            {
                ReadOnlySpan<byte> testBigSpanValue = new ReadOnlySpan<byte>(new byte[2048]);
                testStatus.SetFingerprint(testBigSpanValue);
            });
            
            Assert.IsType<ArgumentOutOfRangeException>(caughtException);

            caughtException = Record.Exception(() =>
            {
                Span<byte> testSmallSpanValue = new Span<byte>(new byte[8]);
                testStatus.GetFingerprint(testSmallSpanValue);
            });
            Assert.IsType<ArgumentOutOfRangeException>(caughtException);
        }
        
    }
}